package com.xiaoshuidi.cloud.module.bpm.service.swo;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xiaoshuidi.cloud.framework.common.exception.ServiceException;
import com.xiaoshuidi.cloud.framework.common.pojo.CommonResult;
import com.xiaoshuidi.cloud.framework.common.pojo.PageResult;
import com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils;
import com.xiaoshuidi.cloud.framework.oss.core.OosFileService;
import com.xiaoshuidi.cloud.framework.security.core.LoginUser;
import com.xiaoshuidi.cloud.module.bpm.controller.admin.swo.vo.*;
import com.xiaoshuidi.cloud.module.bpm.controller.admin.sworecord.vo.GetSwoTicketRespVO;
import com.xiaoshuidi.cloud.module.bpm.controller.admin.sworecord.vo.SwoTicketAssignRecordCreateReqVO;
import com.xiaoshuidi.cloud.module.bpm.controller.admin.sworecord.vo.SwoTicketAssignRecordRespVO;
import com.xiaoshuidi.cloud.module.bpm.controller.app.swo.vo.AppSwoTicketCreateReqVO;
import com.xiaoshuidi.cloud.module.bpm.controller.app.swo.vo.AppSwoTicketRespVO;
import com.xiaoshuidi.cloud.module.bpm.controller.app.swo.vo.SwoTicketAppraiseReqVO;
import com.xiaoshuidi.cloud.module.bpm.convert.swo.SwoTicketConvert;
import com.xiaoshuidi.cloud.module.bpm.convert.sworecord.SwoTicketAssignRecordConvert;
import com.xiaoshuidi.cloud.module.bpm.dal.dataobject.swo.SwoTicketDO;
import com.xiaoshuidi.cloud.module.bpm.dal.dataobject.sworecord.SwoTicketAssignRecordDO;
import com.xiaoshuidi.cloud.module.bpm.dal.mysql.swo.SwoTicketMapper;
import com.xiaoshuidi.cloud.module.bpm.enums.MaintenanceFlagEnum;
import com.xiaoshuidi.cloud.module.bpm.enums.TicketFromEnum;
import com.xiaoshuidi.cloud.module.bpm.enums.reserveticket.UrgencyLevelEnum;
import com.xiaoshuidi.cloud.module.bpm.enums.swo.*;
import com.xiaoshuidi.cloud.module.bpm.framework.utils.CodeUtil;
import com.xiaoshuidi.cloud.module.bpm.job.delayjob.TicketDelayJobService;
import com.xiaoshuidi.cloud.module.bpm.job.delayjob.TicketDelayJobTypeEnum;
import com.xiaoshuidi.cloud.module.bpm.service.message.BpmMessageService;
import com.xiaoshuidi.cloud.module.bpm.service.message.dto.MessageWhenSwoTicketRemindReqDTO;
import com.xiaoshuidi.cloud.module.bpm.service.message.dto.MessageWhenSwoTicketReqDTO;
import com.xiaoshuidi.cloud.module.bpm.service.sworecord.SwoTicketAssignRecordService;
import com.xiaoshuidi.cloud.module.contract.api.ContractRentOrderApi;
import com.xiaoshuidi.cloud.module.contract.enums.bill.OrderDiscountEnum;
import com.xiaoshuidi.cloud.module.contract.enums.bill.OrderFeeSortTypeEnum;
import com.xiaoshuidi.cloud.module.contract.enums.bill.OrderTypeEnum;
import com.xiaoshuidi.cloud.module.contract.vo.OrderFeesMeterVo;
import com.xiaoshuidi.cloud.module.contract.vo.RentOrderCreateMeterReq;
import com.xiaoshuidi.cloud.module.contract.vo.RentOrderSowTicketVO;
import com.xiaoshuidi.cloud.module.member.api.user.MemberUserApi;
import com.xiaoshuidi.cloud.module.member.api.user.dto.MemberUserRespDTO;
import com.xiaoshuidi.cloud.module.rooms.api.apartment.ApartmentApi;
import com.xiaoshuidi.cloud.module.rooms.api.apartment.dto.ApartmentListDto;
import com.xiaoshuidi.cloud.module.rooms.api.roomhouse.RoomHouseApi;
import com.xiaoshuidi.cloud.module.rooms.vo.RoomHouseRespVo;
import com.xiaoshuidi.cloud.module.system.api.user.AdminUserApi;
import com.xiaoshuidi.cloud.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.xiaoshuidi.cloud.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.xiaoshuidi.cloud.module.bpm.enums.ErrorCodeConstants.*;


/**
 * wo 服务工单申请 Service 实现类
 *
 * @author 芋道源码
 */
@Slf4j
@Service
@Validated
public class SwoTicketServiceImpl implements SwoTicketService {


    @Resource
    private SwoTicketMapper swoTicketMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private SwoTicketAssignRecordService swoTicketAssignRecordService;
    @Resource
    private BpmMessageService bpmMessageService;
    @Resource
    private RoomHouseApi roomHouseApi;
    @Resource
    private MemberUserApi memberUserApi;
    @Resource
    private ApartmentApi apartmentApi;
    @Resource
    private ContractRentOrderApi contractRentOrderApi;
    @Lazy
    @Resource
    private TicketDelayJobService ticketDelayJobService;
    @Resource
    private CodeUtil codeUtil;
    @Autowired
    private OosFileService oosFileService;
    private static final String PREFIX = "WO";




    @Override
    public int getStatisticsOfUNAssignStatus() {
        int count = swoTicketMapper.getStatisticsOfUNAssignStatus();
        return count;

    }


    @Override
    public Map getStatisticsForProcessStatus() {

        Map map = new HashMap();

        int unprocess = swoTicketMapper.getStatisticsOfUNProcess();
        int processed = swoTicketMapper.getStatisticsOfProcessed();
        int notprocess = swoTicketMapper.getStatisticsOfNOTProcess();

        map.put("unprocess",unprocess);
        map.put("processed",processed);
        map.put("notprocess",notprocess);

        return map;

    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createSwoTicket(LoginUser loginUser, SwoTicketCreateReqVO createReqVO) {
        log.info("创建服务工单请求参数：{}", JsonUtils.toJsonString(createReqVO));
        SwoTicketDO swoTicket = SwoTicketConvert.INSTANCE.convert(createReqVO);
        swoTicket.setUserId(loginUser.getId());
        swoTicket.setNeedPay(false);
        // 门店信息
        ApartmentListDto apartmentListDto = apartmentApi.getById(createReqVO.getApartmentId()).getCheckedData();

        if(TicketSourceEnum.TENANTS.getValue().equals(createReqVO.getSource()) && MaintenanceFlagEnum.PUBLIC.getValue().equals(createReqVO.getMaintenanceFlag())){
            // 来源是租客, 维修区域是公区, 需要门店id
            Assert.isTrue(ObjectUtil.isNotEmpty(createReqVO.getApartmentId()), "请选择门店");
            swoTicket.setApartmentId(apartmentListDto.getId());
            swoTicket.setRoomFullName(apartmentListDto.getName());
            swoTicket.setDeptId(apartmentListDto.getDeptId());
        }else if(TicketSourceEnum.TENANTS.getValue().equals(createReqVO.getSource()) && MaintenanceFlagEnum.ROOM.getValue().equals(createReqVO.getMaintenanceFlag())){
            // 来源是租客, 维修区域是房间, 需要房源id
            Assert.isTrue(ObjectUtil.isNotEmpty(createReqVO.getRoomId()), "请选择房源");
            RoomHouseRespVo roomHouseRespVo = roomHouseApi.getRoomHouseinfoById(createReqVO.getRoomId()).getCheckedData();
            swoTicket.setApartmentId(roomHouseRespVo.getApartmentId());
            // 房间全名需要加上门店名
            swoTicket.setRoomFullName(roomHouseRespVo.getApartmentName() + swoTicket.getRoomFullName());
            swoTicket.setDeptId(roomHouseRespVo.getDeptId());
        }else if(TicketSourceEnum.INTERIOR.getValue().equals(createReqVO.getSource()) && MaintenanceFlagEnum.PUBLIC.getValue().equals(createReqVO.getMaintenanceFlag())){
            // 来源是内部, 维修区域是公区, 需要门店id, 若未传门店id, 则用当前用户的部门
            if(ObjectUtil.isNotEmpty(createReqVO.getApartmentId())){
                swoTicket.setApartmentId(apartmentListDto.getId());
                swoTicket.setRoomFullName(apartmentListDto.getName());
                swoTicket.setDeptId(apartmentListDto.getDeptId());
            }else{
                swoTicket.setDeptId(loginUser.getDeptId());
            }
        }else if(TicketSourceEnum.INTERIOR.getValue().equals(createReqVO.getSource()) && MaintenanceFlagEnum.ROOM.getValue().equals(createReqVO.getMaintenanceFlag())){
            // 来源是内部, 维修区域是房间, 需要房源id, 若未传房源id, 则用当前用户的部门
            if(ObjectUtil.isNotEmpty(createReqVO.getRoomId())){
                RoomHouseRespVo roomHouseRespVo = roomHouseApi.getRoomHouseinfoById(createReqVO.getRoomId()).getCheckedData();
                swoTicket.setApartmentId(roomHouseRespVo.getApartmentId());
                // 房间全名需要加上门店名
                swoTicket.setRoomFullName(roomHouseRespVo.getApartmentName() + swoTicket.getRoomFullName());
                swoTicket.setDeptId(roomHouseRespVo.getDeptId());
            }else{
                swoTicket.setDeptId(loginUser.getDeptId());
            }
        }
        // 默认待分配
        swoTicket.setStatus(TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus());
        // 默认待处理
        swoTicket.setProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_UNPROCESSED.getProcessStatus());
        // 是否已评价
        swoTicket.setEvaluatedOrNot(false);
        swoTicket.setIsAcceptance(false);
        String woId = codeUtil.nextCode(PREFIX, 6);
        swoTicket.setWoNumber(woId);
        swoTicketMapper.insert(swoTicket);

        // 生成报修记录
        SwoTicketAssignRecordCreateReqVO swoTicketAssignRecordCreateReqVO = new SwoTicketAssignRecordCreateReqVO();
        swoTicketAssignRecordCreateReqVO.setSwoTicketId(swoTicket.getId());
        swoTicketAssignRecordCreateReqVO.setOperatorsUserId(loginUser.getId());
        swoTicketAssignRecordCreateReqVO.setCustomerName(swoTicket.getCustomerName());

        StringBuilder sb = new StringBuilder();
        sb.append(loginUser.getNickname())
                .append(" 手机号:")
                .append(loginUser.getMobile())
                .append("创建了工单, 客户:")
                .append(createReqVO.getCustomerName());
        if(ObjectUtil.isNotEmpty(createReqVO.getResponsibleParty())){
            sb.append(", 指定责任方为:");
            sb.append(ResponsiblePartyEnum.getNameByValue(createReqVO.getResponsibleParty()));
        }
        swoTicketAssignRecordCreateReqVO.setRecord(sb.toString());
        swoTicketAssignRecordService.createSwoTicketAssignRecord(swoTicketAssignRecordCreateReqVO);


        // 如果是维修工单,短信提醒对应门店
        if (SwoServiceTypeEnum.MAINTENANCE.getValue().equals(swoTicket.getServiceType())) {
            // 发送短信
            MessageWhenSwoTicketRemindReqDTO swoTicketRemindReqDTO = new MessageWhenSwoTicketRemindReqDTO()
                    .setParam("")
                    .setMobile(apartmentListDto.getStoreTel())
                    .setRoomFullName(swoTicket.getRoomFullName());
            bpmMessageService.sendMessageWhenSwoTicketRemind(swoTicketRemindReqDTO);
            log.info("维修工单:{},短信提醒对应门店:{},门店id:{}, 请求参数:{}", swoTicket.getId(), apartmentListDto.getName(), apartmentListDto.getId(), JsonUtils.toJsonString(swoTicketRemindReqDTO));
        }
        // 如果服务结束时间是今天，则添加延迟任务，结束时间不是今天的，在定时任务执行时添加进延迟任务执行
        if(LocalDate.now().equals(swoTicket.getServiceTimeEnd().toLocalDate())){
            // 当前时间到任务结束前30分钟时间相差的分钟数
            // 当前时间在任务结束时间之后，不需要提醒
            long minutes = 0;
            if(LocalDateTime.now().isBefore(swoTicket.getServiceTimeEnd().plusMinutes(-30))){
                // 当前时间在任务结束时间的30分钟之前
                minutes = Duration.between(LocalDateTime.now(), swoTicket.getServiceTimeEnd().plusMinutes(-30)).toMillis();
            }else {
                minutes = 1;
            }
            log.info("工单:{},到期时间:{},{}毫秒后执行", swoTicket.getId(), swoTicket.getServiceTimeEnd(), minutes);
            TicketDelayJobParamDTO ticketDelayJobParamDTO = new TicketDelayJobParamDTO()
                    .setSwoTicketId(swoTicket.getId())
                    .setAssigneeId(swoTicket.getAssignee());
            // 添加延迟任务, 在指定时间后执行, 检查任务是否完成, 未完成则需要短信提醒
            ticketDelayJobService.addDelayJob(TicketDelayJobTypeEnum.REPAIR_TIMEOUT_REMINDER, ticketDelayJobParamDTO, minutes, TimeUnit.MILLISECONDS);
        }
        // 返回
        return swoTicket.getId();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSwoTicket(LoginUser loginUser, SwoTicketUpdateReqVO updateReqVO) {
        // 校验存在
        validateSwoTicketExists(updateReqVO.getId());
        // 更新
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(updateReqVO.getId());
        swoTicketDO.setResponsibleParty(updateReqVO.getResponsibleParty());
        swoTicketMapper.updateById(swoTicketDO);

        // 生成报修记录
        SwoTicketAssignRecordCreateReqVO swoTicketAssignRecordCreateReqVO = new SwoTicketAssignRecordCreateReqVO();
        swoTicketAssignRecordCreateReqVO.setSwoTicketId(swoTicketDO.getId());
        swoTicketAssignRecordCreateReqVO.setOperatorsUserId(loginUser.getId());

        StringBuilder sb = new StringBuilder();
        sb.append(loginUser.getNickname())
                .append(" 手机号:")
                .append(loginUser.getMobile())
                .append("修改责任方为:")
                .append(ResponsiblePartyEnum.getNameByValue(updateReqVO.getResponsibleParty()));

        swoTicketAssignRecordCreateReqVO.setRecord(sb.toString());
        swoTicketAssignRecordService.createSwoTicketAssignRecord(swoTicketAssignRecordCreateReqVO);
    }

    @Override
    public void deleteSwoTicket(Long id) {
        // 校验存在
        validateSwoTicketExists(id);
        // 删除
        swoTicketMapper.deleteById(id);
    }

    private void validateSwoTicketExists(Long id) {
        if (swoTicketMapper.selectById(id) == null) {
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
    }

    @Override
    public GetSwoTicketRespVO getSwoTicket(Long id) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(id);
        SwoTicketRespVO ticketRespVO = SwoTicketConvert.INSTANCE.convert(swoTicketDO);
        ticketRespVO.setRepairType(swoTicketDO.getRepairType());
        ticketRespVO.setProcessStatus(Integer.valueOf(0).equals(swoTicketDO.getRepairType()) ? null :swoTicketDO.getRepairType());
        ticketRespVO.setAttachments(ObjectUtil.isEmpty(swoTicketDO.getAttachments()) ? "" : swoTicketDO.getAttachments());
        // 查询对应的指派记录
        List<SwoTicketAssignRecordDO> assignRecordDOS = swoTicketAssignRecordService.getByTicketId(id);
        List<SwoTicketAssignRecordRespVO> assignRecordRespVOS = SwoTicketAssignRecordConvert.INSTANCE.convert(assignRecordDOS);
        // 获取跟进人名字
        ticketRespVO.setStatusName(this.convertStatus(ticketRespVO.getStatus()));
        if(ObjectUtil.isNotEmpty(ticketRespVO.getAssignee())) {
            AdminUserRespDTO checkedData = adminUserApi.getUser(ticketRespVO.getAssignee()).getCheckedData();
            ticketRespVO.setFollowUpUserName(ObjectUtil.isNotEmpty(checkedData) ? checkedData.getNickname() : null);
        }
        if(ObjectUtil.isNotEmpty(ticketRespVO.getApartmentId())){
            ApartmentListDto apartmentListDto = apartmentApi.getById(ticketRespVO.getApartmentId()).getCheckedData();
            ticketRespVO.setApartmentName(ObjectUtil.isNotEmpty(apartmentListDto) ? apartmentListDto.getName() : null);
        }

        // 查询对应的账单记录
        List<SwoTicketRespVO.ReceiptsRecord> records = new ArrayList<>();
        List<RentOrderSowTicketVO> checkedData = contractRentOrderApi.getSowTicketOrder(id).getCheckedData();
        log.info("服务工单:{}账单记录:{}", id, JsonUtils.toJsonString(checkedData));
        checkedData.forEach(rentOrderSowTicketVO -> {
            SwoTicketRespVO.ReceiptsRecord receiptsRecord = new SwoTicketRespVO.ReceiptsRecord()
                    .setId(rentOrderSowTicketVO.getUuid())
                    .setExtrasAmount(rentOrderSowTicketVO.getExtrasAmount())
                    .setPayMethod(rentOrderSowTicketVO.getPayMethod())
                    .setActualPayTime(rentOrderSowTicketVO.getActualPayTime())
                    .setTradeSerialNo(rentOrderSowTicketVO.getTradeSerialNo());
            records.add(receiptsRecord);
        });
        ticketRespVO.setRecords(records);
        GetSwoTicketRespVO respVO = new GetSwoTicketRespVO();
        respVO.setSwoTicketRespVO(ticketRespVO);
        respVO.setSwoTicketAssignRecordRespVOS(assignRecordRespVOS);
        return respVO;
    }

    @Override
    public List<SwoTicketDO> getSwoTicketList(Collection<Long> ids) {
        return swoTicketMapper.selectBatchIds(ids);
    }

    /**
     * 服务工单分页
     * @param pageReqVO 分页查询
     * @return
     */
    @Override
    public PageResult<SwoTicketRespVO> getSwoTicketPage(SwoTicketPageReqVO pageReqVO) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ObjectUtil.isNotEmpty(pageReqVO.getStatus()), SwoTicketDO::getStatus, pageReqVO.getStatus());
        wrapper.eq(ObjectUtil.isNotEmpty(pageReqVO.getApartmentId()), SwoTicketDO::getApartmentId, pageReqVO.getApartmentId());
        wrapper.eq(ObjectUtil.isNotEmpty(pageReqVO.getServiceType()), SwoTicketDO::getServiceType, pageReqVO.getServiceType());
        wrapper.eq(ObjectUtil.isNotEmpty(pageReqVO.getAssigneeUserId()), SwoTicketDO::getAssignee, pageReqVO.getAssigneeUserId());
        wrapper.and(ObjectUtil.isNotEmpty(pageReqVO.getSearchBox()), w ->
                w.like(SwoTicketDO::getCustomerName, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getCustomerMobile, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getContactMobile, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getRoomFullName, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getWoNumber, pageReqVO.getSearchBox())
        );
        if(ObjectUtil.isNotEmpty(pageReqVO.getServiceTimeStart()) && ObjectUtil.isNotEmpty(pageReqVO.getServiceTimeEnd())){
            LocalDate end = pageReqVO.getServiceTimeEnd();
            LocalDateTime dayEnd = LocalDateTime.of(end.getYear(), end.getMonth(), end.getDayOfMonth(), 23, 59, 59);
            wrapper.and(p -> p.ge(SwoTicketDO::getServiceTimeStart, pageReqVO.getServiceTimeStart().atStartOfDay()).le(SwoTicketDO::getServiceTimeEnd, dayEnd));
        }
        if(ObjectUtil.isAllNotEmpty(pageReqVO.getCreateTimeStart(), pageReqVO.getCreateTimeEnd())) {
            final LocalDate createTimeStart = pageReqVO.getCreateTimeStart();
            final LocalDate createTimeEnd = pageReqVO.getCreateTimeEnd();
            LocalDateTime start = createTimeStart.atStartOfDay();
            LocalDateTime end = LocalDateTime.of(createTimeEnd.getYear(), createTimeEnd.getMonth(), createTimeEnd.getDayOfMonth(), 23, 59, 59);
            wrapper.and(p -> p.between(SwoTicketDO::getCreateTime, start, end));
        }
            // 完成时间过滤条件
        if(ObjectUtil.isAllNotEmpty(pageReqVO.getCompleteTimeStart(), pageReqVO.getCompleteTimeEnd())){
            wrapper.and(p -> p.ge(SwoTicketDO::getCompleteTime, pageReqVO.getCompleteTimeStart()).le(SwoTicketDO::getServiceTimeEnd, pageReqVO.getCompleteTimeEnd()));
        }
        wrapper.orderByDesc(SwoTicketDO::getCreateTime);
        Page<SwoTicketDO> page = swoTicketMapper.selectPage(new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), wrapper);
        List<SwoTicketRespVO> respVOS = SwoTicketConvert.INSTANCE.convert(page.getRecords());
        List<Long> apartmentIds = page.getRecords().stream().filter(p -> ObjectUtil.isNotEmpty(p.getApartmentId())).map(SwoTicketDO::getApartmentId).distinct().collect(Collectors.toList());
        Map<Long, String> apartmentMap = new HashMap<>();
        if(ObjectUtil.isNotEmpty(apartmentIds)){
            List<ApartmentListDto> apartmentListDtos = apartmentApi.listByIds(apartmentIds).getCheckedData();
            apartmentMap = apartmentListDtos.stream().collect(Collectors.toMap(ApartmentListDto::getId, ApartmentListDto::getName));
        }
        Map<Long, String> finalApartmentMap = apartmentMap;
        respVOS.stream().forEach(swoTicketRespVO -> {
            swoTicketRespVO.setResponsibleParty(Integer.valueOf(0).equals(swoTicketRespVO.getResponsibleParty()) ? null : swoTicketRespVO.getResponsibleParty());
            if(ObjectUtil.isNotEmpty(swoTicketRespVO.getServiceType())){
                swoTicketRespVO.setServiceTypeName(SwoServiceTypeEnum.getNameByValue(swoTicketRespVO.getServiceType()));
            }
            if(ObjectUtil.isNotEmpty(swoTicketRespVO.getAssignee())) {
                AdminUserRespDTO checkedData = adminUserApi.getUser(swoTicketRespVO.getAssignee()).getCheckedData();
                swoTicketRespVO.setFollowUpUserName(ObjectUtil.isNotEmpty(checkedData) ? checkedData.getNickname() : null);
            }
            if(ObjectUtil.isNotEmpty(swoTicketRespVO.getApartmentId())){
                swoTicketRespVO.setApartmentName(finalApartmentMap.get(swoTicketRespVO.getApartmentId()));
            }
        });
        return new PageResult<>(respVOS, page.getTotal());
    }

    @Override
    public List<SwoTicketDO> getSwoTicketList(SwoTicketExportReqVO exportReqVO) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ObjectUtil.isNotEmpty(exportReqVO.getStatus()), SwoTicketDO::getStatus, exportReqVO.getStatus());
        wrapper.eq(ObjectUtil.isNotEmpty(exportReqVO.getApartmentId()), SwoTicketDO::getApartmentId, exportReqVO.getApartmentId());
        wrapper.eq(ObjectUtil.isNotEmpty(exportReqVO.getServiceType()), SwoTicketDO::getServiceType, exportReqVO.getServiceType());
        wrapper.eq(ObjectUtil.isNotEmpty(exportReqVO.getAssigneeUserId()), SwoTicketDO::getAssignee, exportReqVO.getAssigneeUserId());
        wrapper.and(ObjectUtil.isNotEmpty(exportReqVO.getSearchBox()), w ->
                w.like(SwoTicketDO::getCustomerName, exportReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getCustomerMobile, exportReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getContactMobile, exportReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getRoomFullName, exportReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getWoNumber, exportReqVO.getSearchBox())
        );
        if(ObjectUtil.isNotEmpty(exportReqVO.getServiceTimeStart()) && ObjectUtil.isNotEmpty(exportReqVO.getServiceTimeEnd())){
            LocalDate end = exportReqVO.getServiceTimeEnd();
            LocalDateTime dayEnd = LocalDateTime.of(end.getYear(), end.getMonth(), end.getDayOfMonth(), 23, 59, 59);
            wrapper.and(p -> p.ge(SwoTicketDO::getServiceTimeStart, exportReqVO.getServiceTimeStart().atStartOfDay()).le(SwoTicketDO::getServiceTimeEnd, dayEnd));
        }
        if(ObjectUtil.isAllNotEmpty(exportReqVO.getCreateTimeStart(), exportReqVO.getCreateTimeEnd())) {
            final LocalDate createTimeStart = exportReqVO.getCreateTimeStart();
            final LocalDate createTimeEnd = exportReqVO.getCreateTimeEnd();
            LocalDateTime start = createTimeStart.atStartOfDay();
            LocalDateTime end = LocalDateTime.of(createTimeEnd.getYear(), createTimeEnd.getMonth(), createTimeEnd.getDayOfMonth(), 23, 59, 59);
            wrapper.and(p -> p.between(SwoTicketDO::getCreateTime, start, end));
        }
        // 完成时间过滤条件
        if(ObjectUtil.isAllNotEmpty(exportReqVO.getCompleteTimeStart(), exportReqVO.getCompleteTimeEnd())){
            wrapper.and(p -> p.ge(SwoTicketDO::getCompleteTime, exportReqVO.getCompleteTimeStart()).le(SwoTicketDO::getServiceTimeEnd, exportReqVO.getCompleteTimeEnd()));
        }
        wrapper.orderByDesc(SwoTicketDO::getCreateTime);
        return swoTicketMapper.selectList(wrapper);
    }

    /**
     * 服务工单 - 指派
     * @param userId
     * @param swoAssignmentReqVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long assignment(Long userId, SwoAssignmentReqVO swoAssignmentReqVO) {
        // 校验存在
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(swoAssignmentReqVO.getSwoTicketId());
        // 原责任方
        Integer responsibleParty = swoTicketDO.getResponsibleParty();
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        // 如果来源是租客端, 并且责任方为空的话, 需要指定责任方
        if(TicketFromEnum.TENANT.getValue().equals(swoTicketDO.getTicketFrom())
                && ObjectUtil.isEmpty(swoTicketDO.getResponsibleParty())
                && ObjectUtil.isEmpty(swoAssignmentReqVO.getResponsibleParty())){
            throw exception(SWO_TICKET_RESPONSIBLEPARTY_NOT_EXISTS);
        }

        swoTicketDO.setStatus(TicketServiceStatusEnum.IN_PROGRESS.getStatus());
        swoTicketDO.setProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_ONPROCESS.getProcessStatus());
        swoTicketDO.setAssignee(swoAssignmentReqVO.getAssigneeUserId());
        swoTicketDO.setIsAcceptance(false);
        if(ObjectUtil.isNotEmpty(swoAssignmentReqVO.getRequiredCompletionTime())){
            swoTicketDO.setRequiredCompletionTime(swoAssignmentReqVO.getRequiredCompletionTime());
        }
        // 指定责任方
        if(ObjectUtil.isNotEmpty(swoAssignmentReqVO.getResponsibleParty())){
            swoTicketDO.setResponsibleParty(swoAssignmentReqVO.getResponsibleParty());
        }
        if(ObjectUtil.isNotEmpty(swoAssignmentReqVO.getAttachments())){
            if(!SwoServiceStatusEnum.PENDING_ORDERS.getValue().equals(swoTicketDO.getStatus())){
                throw exception(SWO_TICKET_ATTACHMENTS_ERROR);
            }
            // 指派时的附件
            swoTicketDO.setButlerAttachments(swoAssignmentReqVO.getAttachments());
        }
        swoTicketMapper.updateById(swoTicketDO);

        AdminUserRespDTO loginUserInfo = adminUserApi.getUser(userId).getCheckedData();

        SwoTicketAssignRecordCreateReqVO createReqVO = new SwoTicketAssignRecordCreateReqVO();
        createReqVO.setSwoTicketId(swoAssignmentReqVO.getSwoTicketId());
        createReqVO.setOperatorsUserId(userId);
        createReqVO.setAssignee(swoAssignmentReqVO.getAssigneeUserId());
        createReqVO.setCustomerName(swoAssignmentReqVO.getCustomerName());
        createReqVO.setRemark(swoAssignmentReqVO.getRemark());

        List<SwoTicketAssignRecordDO> byTicketId = swoTicketAssignRecordService.getByTicketId(swoAssignmentReqVO.getSwoTicketId());

        StringBuilder sb = new StringBuilder();
        sb.append(loginUserInfo.getNickname());
        if(ObjectUtil.isEmpty(byTicketId)){
            sb.append("将工单指派给了 ");
        }else {
            sb.append("将工单转派给了 ");
        }
        sb.append(swoAssignmentReqVO.getAssigneeUserName());
        // 如果责任方有变更
        if(ObjectUtil.isNotEmpty(swoAssignmentReqVO.getResponsibleParty()) && !swoAssignmentReqVO.getResponsibleParty().equals(responsibleParty) ){
            sb.append(", 将责任方变更为 ");
            sb.append(ResponsiblePartyEnum.getNameByValue(swoAssignmentReqVO.getResponsibleParty()));
        }
        createReqVO.setRecord(sb.toString());
        Long assignRecordId = swoTicketAssignRecordService.createSwoTicketAssignRecord(createReqVO);

        if(ObjectUtil.isNotEmpty(swoAssignmentReqVO.getAttachments())){
            sb = new StringBuilder();
            sb.append(loginUserInfo.getNickname());
            sb.append("上传了附件:");
            createReqVO.setRecord(sb.toString());
            createReqVO.setAttachments(swoAssignmentReqVO.getAttachments());
            swoTicketAssignRecordService.createSwoTicketAssignRecord(createReqVO);
        }

        ApartmentListDto apartmentListDto = apartmentApi.getById(swoTicketDO.getApartmentId()).getCheckedData();
        // 维修工单单独提供短信提醒模板
        if (SwoServiceTypeEnum.MAINTENANCE.getValue().equals(swoTicketDO.getServiceType())) {

            // 指派后, 如果是维修工单, 则创建延迟任务, 延迟50分钟, 消息指定工单Id
            ticketDelayJobService.addDelayJob(TicketDelayJobTypeEnum.REMINDER_OF_ACCEPTANCE_TIMEOUT,
                    new TicketDelayJobParamDTO().setSwoTicketId(swoTicketDO.getId()).setAssigneeId(swoTicketDO.getAssignee()),
                    50l,
                    TimeUnit.MINUTES);
            log.info("为维修工单ID:{}创建受理短信提醒,50分钟后提醒", swoAssignmentReqVO.getSwoTicketId());
            MessageWhenSwoTicketRemindReqDTO swoTicketRemindReqDTO = new MessageWhenSwoTicketRemindReqDTO()
                    .setUserId(swoAssignmentReqVO.getAssigneeUserId())
                    .setParam("")
                    .setRoomFullName(swoTicketDO.getRoomFullName());
            bpmMessageService.sendMessageWhenSwoTicketRemind(swoTicketRemindReqDTO);
            log.info("维修工单:{}指派提醒已发送", swoAssignmentReqVO.getSwoTicketId());
        } else {
            MessageWhenSwoTicketReqDTO reserveTicketReqDTO = new MessageWhenSwoTicketReqDTO()
                    .setUserId(swoAssignmentReqVO.getAssigneeUserId())
                    .setRoomFullName(swoTicketDO.getRoomFullName());
            bpmMessageService.sendMessageWhenSwoTicket(reserveTicketReqDTO);
            log.info("工单:{}指派提醒已发送", swoAssignmentReqVO.getSwoTicketId());
        }

        // 检查是否指定了要求完工时间
        if(ObjectUtil.isNotEmpty(swoAssignmentReqVO.getRequiredCompletionTime())){
            // 完工时间是否在当前时间之前
            LocalDateTime requiredCompletionTime = swoAssignmentReqVO.getRequiredCompletionTime();
            if(requiredCompletionTime.isBefore(LocalDateTime.now())){
                throw new ServiceException(SWO_TICKET_REQUIRED_COMPLETION_TIME_ERROR);
            }
            // 完工时间是否是当天
            if(LocalDateTimeUtil.isSameDay(requiredCompletionTime, LocalDateTime.now())){
                // 点前时间和完工时间间隔, 分钟
                long minutes = LocalDateTimeUtil.between(LocalDateTime.now(), requiredCompletionTime).toMinutes();
                // 间隔在两个小时之内, 1分钟后发送短信提醒
                if(minutes <= 120){
                    minutes = 1L;
                }else {
                    // 间隔在两个小时以上, 计算当前时间和完工时间前两个小时之间的间隔
                    minutes = LocalDateTimeUtil.between(LocalDateTime.now(), requiredCompletionTime.minusHours(2)).toMinutes();
                }
                // 发送延迟消息
                TicketDelayJobParamDTO ticketDelayJobParamDTO = new TicketDelayJobParamDTO();
                ticketDelayJobParamDTO.setSwoTicketId(swoTicketDO.getId());
                ticketDelayJobParamDTO.setAssigneeId(swoTicketDO.getAssignee());
                ticketDelayJobParamDTO.setReminderType(1);
                ticketDelayJobService.addDelayJob(TicketDelayJobTypeEnum.COMPLETION_TIMEOUT_REMINDER, ticketDelayJobParamDTO, minutes, TimeUnit.MINUTES);
                log.info("[服务工单-完工超时提醒] 添加延迟任务成功,工单ID:{}, {}分钟后执行", swoTicketDO.getId(), minutes);
            }
        }
        return assignRecordId;
    }

    /**
     * 结束wo 服务工单申请
     *
     * @param userId
     * @param stopSwoTicketReqVO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean stop(Long userId, StopSwoTicketReqVO stopSwoTicketReqVO) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(stopSwoTicketReqVO.getSwoTicketId());
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        // 状态改为已完成
        swoTicketDO.setStatus(TicketServiceStatusEnum.COMPLETED.getStatus());
        swoTicketDO.setCompleteTime(LocalDateTime.now());
        // 进度改为已处理
        swoTicketDO.setProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_PROCESSED.getProcessStatus());
        List<String> urls = stopSwoTicketReqVO.getUrls();
        String finishRemark = stopSwoTicketReqVO.getFinishRemark();
        Integer repairType = stopSwoTicketReqVO.getRepairType();
        if(CollectionUtil.isNotEmpty(urls)){
            if(urls.size() >9){
                throw exception(SWO_TICKET_PICTURE_TOO_MANY);
            }
            String join = String.join(";", urls);
            swoTicketDO.setFinishPicture(join);
        }
        if(ObjectUtil.isNotEmpty(finishRemark)){
            swoTicketDO.setFinishRemark(finishRemark);
        }
        if(ObjectUtil.isNotEmpty(repairType)){
            swoTicketDO.setRepairType(repairType);
        }
        swoTicketMapper.updateById(swoTicketDO);

        AdminUserRespDTO loginUserInfo = adminUserApi.getUser(userId).getCheckedData();

        SwoTicketAssignRecordCreateReqVO createReqVO = new SwoTicketAssignRecordCreateReqVO();
        createReqVO.setSwoTicketId(swoTicketDO.getId());
        createReqVO.setOperatorsUserId(userId);
        createReqVO.setAssignee(swoTicketDO.getAssignee());
        createReqVO.setCustomerName(swoTicketDO.getCustomerName());
        createReqVO.setRemark(stopSwoTicketReqVO.getRemark());

        StringBuilder sb = new StringBuilder();
        sb.append(loginUserInfo.getNickname());
        sb.append(" 结束了工单");
        createReqVO.setRecord(sb.toString());
        swoTicketAssignRecordService.createSwoTicketAssignRecord(createReqVO);
        return Boolean.TRUE;
    }

    /**
     * 上传附件
     * @param uploadAnnex
     * @return
     */
    @Override
    public Boolean uploadAnnex(UploadAnnex uploadAnnex) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(uploadAnnex.getId());
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        String attachments = swoTicketDO.getAttachments();
        if(!attachments.endsWith(";")){
            attachments += ";";
        }
        if(attachments.length() + uploadAnnex.getUploadFIle().length() > 3000){
            throw exception(RESERVE_TICKET_ATTACHMENTS_TOO_LONG);
        }
        swoTicketDO.setAttachments(attachments + uploadAnnex.getUploadFIle());
        swoTicketMapper.updateById(swoTicketDO);
        return Boolean.TRUE;
    }

    /**
     * 服务工单 - 催单
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean reminders(Long id) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(id);
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        Long assignee = swoTicketDO.getAssignee();
        if(ObjectUtil.isEmpty(assignee)){
            throw exception(RESERVE_TICKET_ASSIGNEE_NOT_EXISTS);
        }
        AdminUserRespDTO adminUserRespDTO = adminUserApi.getUser(assignee).getCheckedData();

        // 发送短信
        MessageWhenSwoTicketReqDTO reserveTicketReqDTO = new MessageWhenSwoTicketReqDTO()
                .setUserId(adminUserRespDTO.getId())
                .setRoomFullName(swoTicketDO.getRoomFullName());
        bpmMessageService.sendMessageWhenSwoTicket(reserveTicketReqDTO);
        return Boolean.TRUE;
    }

    /**
     * 分页状态数量统计
     * @return
     */
    @Override
    public SwoTicketStatusCountRespVO statusCount(Integer serviceType) {
        SwoTicketStatusCountRespVO respVO = new SwoTicketStatusCountRespVO();
        // 待指派
        respVO.setToBeAssigned(this.countByStatus(TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus(), serviceType));
        // 进行中
        respVO.setInProgress(this.countByStatus(TicketServiceStatusEnum.IN_PROGRESS.getStatus(), serviceType));
        // 已完成
        respVO.setCompleted(this.countByStatus(TicketServiceStatusEnum.COMPLETED.getStatus(), serviceType));
        // 待处理
        respVO.setUnprocessed(this.countByProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_UNPROCESSED.getProcessStatus(), serviceType));
        // 处理中
        respVO.setOnProcess(this.countByProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_ONPROCESS.getProcessStatus(), serviceType));
        // 已处理
        respVO.setProcessed(this.countByProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_PROCESSED.getProcessStatus(), serviceType));
        // 不处理
        respVO.setNotProcess(this.countByProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_NOTPROCESS.getProcessStatus(), serviceType));

        return respVO;
    }

    /**
     * 租客端 - 申请服务工单
     * @param loginUser
     * @param appSwoTicketCreateReqVO
     * @return
     */
    @Override
    public Long appCreateSwoTicket(LoginUser loginUser, AppSwoTicketCreateReqVO appSwoTicketCreateReqVO) {
        SwoTicketCreateReqVO createReqVO = new SwoTicketCreateReqVO();
        BeanUtil.copyProperties(appSwoTicketCreateReqVO, createReqVO);
        // 设置默认维修区域
        createReqVO.setMaintenanceFlag(MaintenanceFlagEnum.ROOM.getValue());
        // 补充门店id
        Long roomId = createReqVO.getRoomId();
        RoomHouseRespVo roomHouseRespVo = roomHouseApi.getRoomHouseinfoById(roomId).getCheckedData();
        createReqVO.setApartmentId(roomHouseRespVo.getApartmentId());
        createReqVO.setDeptId(roomHouseRespVo.getDeptId());
        // 默认处理等级 正常
        createReqVO.setLevel(Integer.parseInt(UrgencyLevelEnum.NORMAL.getValue()));
        // 查询租客名字
        List<MemberUserRespDTO> checkedData = memberUserApi.getUsersByIds(Arrays.asList(loginUser.getId())).getCheckedData();
        if(ObjectUtil.isNotEmpty(checkedData)){
            createReqVO.setCustomerName(checkedData.get(0).getNickname());
        }
        // 工单来源为租客端
        createReqVO.setTicketFrom(TicketFromEnum.TENANT.getValue());
        createReqVO.setSource(1);
        return this.createSwoTicket(loginUser, createReqVO);
    }

    @Override
    public PageResult<AppSwoTicketRespVO> appGetSwoTicketPage(String customerMobile, Integer pageNo, Integer pageSize) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ObjectUtil.isNotEmpty(customerMobile), SwoTicketDO::getCustomerMobile, customerMobile);
        wrapper.orderByAsc(SwoTicketDO::getStatus);
        wrapper.orderByDesc(SwoTicketDO::getCreateTime);
        Page<SwoTicketDO> page = swoTicketMapper.selectPage(new Page<>(pageNo, pageSize), wrapper);
        List<AppSwoTicketRespVO> respVOS = page.getRecords().stream().map(p -> {
            AppSwoTicketRespVO appSwoTicketRespVO = new AppSwoTicketRespVO();
            BeanUtil.copyProperties(p, appSwoTicketRespVO);
            appSwoTicketRespVO.setStatusName(convertStatus(appSwoTicketRespVO.getStatus()));
            return appSwoTicketRespVO;
        }).collect(Collectors.toList());
        return new PageResult<>(respVOS, page.getTotal());
    }

    /**
     * 租客端 - 取消服务工单
     *
     * @param loginUser
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean appCancelSwoTicket(LoginUser loginUser, Long id) {
        // 校验存在
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(id);
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        // 工单状态改为已完成
        swoTicketDO.setStatus(TicketServiceStatusEnum.COMPLETED.getStatus());
        swoTicketDO.setProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_PROCESSED.getProcessStatus());
        swoTicketMapper.updateById(swoTicketDO);
        // 生成操作记录
        SwoTicketAssignRecordCreateReqVO swoTicketAssignRecordCreateReqVO = new SwoTicketAssignRecordCreateReqVO();
        swoTicketAssignRecordCreateReqVO.setSwoTicketId(swoTicketDO.getId());
        swoTicketAssignRecordCreateReqVO.setOperatorsUserId(loginUser.getId());
        swoTicketAssignRecordCreateReqVO.setCustomerName(swoTicketDO.getCustomerName());

        StringBuilder sb = new StringBuilder();
        sb.append(loginUser.getNickname())
                .append(" 取消了工单");
        swoTicketAssignRecordCreateReqVO.setRecord(sb.toString());
        swoTicketAssignRecordService.createSwoTicketAssignRecord(swoTicketAssignRecordCreateReqVO);
        return Boolean.TRUE;
    }

    /**
     * 租客端 - 服务工单评价
     * @param swoTicketAppraiseReqVO
     * @return
     */
    @Override
    public Boolean swoTicketAppraise(SwoTicketAppraiseReqVO swoTicketAppraiseReqVO) {
        // 校验存在
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(swoTicketAppraiseReqVO.getId());
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        swoTicketDO.setRating(swoTicketAppraiseReqVO.getRating());
        swoTicketDO.setComments(swoTicketAppraiseReqVO.getComments());
        swoTicketDO.setEvaluatedOrNot(true);
        swoTicketMapper.updateById(swoTicketDO);
        return Boolean.TRUE;
    }

    @Override
    public Long getTotalNumber() {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.in(SwoTicketDO::getStatus, Arrays.asList(TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus(), TicketServiceStatusEnum.IN_PROGRESS.getStatus()));
        return swoTicketMapper.selectCount(wrapper);
    }

    /**
     * 未处理完成的紧急报修的工单数量
     * @return
     */
    @Override
    public Long urgentRepariNum() {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        // 待处理和处理中的
        wrapper.in(SwoTicketDO::getProcessStatus, Arrays.asList(
                TicketServiceProcessStatusEnum.PROCESS_STATUS_UNPROCESSED.getProcessStatus(),
                TicketServiceProcessStatusEnum.PROCESS_STATUS_ONPROCESS.getProcessStatus()
        ));
        // 紧急的
        // wrapper.eq(SwoTicketDO::getLevel, UrgencyLevelEnum.URGENT.getValue());
        return swoTicketMapper.selectCount(wrapper);
    }

    /**
     * 服务工单受理
     * @param swoAcceptanceReqVO
     */
    @Override
    public Boolean acceptance(SwoAcceptanceReqVO swoAcceptanceReqVO) {
        List<String> urls = swoAcceptanceReqVO.getUrls();
        Long swoTicketId = swoAcceptanceReqVO.getSwoTicketId();
        String remark = swoAcceptanceReqVO.getRemark();
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(swoTicketId);
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        swoTicketDO.setAcceptanceRemark(remark);
        if(urls.size() > 9){
            throw exception(SWO_TICKET_PICTURE_TOO_MANY);
        }
        String join = String.join(";", urls);
        swoTicketDO.setAcceptancePicture(join);
        swoTicketDO.setIsAcceptance(true);
//        LocalDateTime actualProcessingTime = swoAcceptanceReqVO.getActualProcessingTime();
//        if(ObjectUtil.isNotEmpty(actualProcessingTime) && actualProcessingTime.isBefore(LocalDateTime.now())){
//            throw exception(ACTUAL_PROCESSING_TIME_ERROR);
//        }
        swoTicketDO.setActualProcessingTime(LocalDateTime.now());
        swoTicketMapper.updateById(swoTicketDO);
        return true;
    }

    /**
     * 回显信息
     * @param swoTicketId
     * @return
     */
    @Override
    public SwoAcceptanceReqVO getAcceptanceInfo(Long swoTicketId) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(swoTicketId);
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        SwoAcceptanceReqVO swoAcceptanceReqVO =new SwoAcceptanceReqVO();

            swoAcceptanceReqVO.setRemark(swoTicketDO.getAcceptanceRemark());
            String acceptancePicture = swoTicketDO.getAcceptancePicture();
            if(ObjectUtil.isNotEmpty(acceptancePicture)){
                List<String> list = Arrays.asList(acceptancePicture.split(";"));
                swoAcceptanceReqVO.setUrls(list);
            }
            swoAcceptanceReqVO.setFinishRemark(swoTicketDO.getFinishRemark());
            String finishPicture = swoTicketDO.getFinishPicture();
            if(ObjectUtil.isNotEmpty(finishPicture)){
                List<String> finishList = Arrays.asList(finishPicture.split(";"));
                swoAcceptanceReqVO.setFinishUrls(finishList);
            }

        return swoAcceptanceReqVO;
    }

    @Override
    public PageResult<SwoTicketRespVO> getSwoTicketPageList(Long loginUserId, SwoTicketPageListReqVO pageReqVO) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SwoTicketDO::getAssignee, loginUserId);
        if(ObjectUtil.isNotEmpty(pageReqVO.getStatus())){
            if(Objects.equals(pageReqVO.getStatus(), SwoServiceStatusEnum.PENDING_ORDERS.getValue())){
                wrapper.eq(SwoTicketDO :: getIsAcceptance,false).eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.IN_PROGRESS.getStatus());
            } else if(Objects.equals(pageReqVO.getStatus(), SwoServiceStatusEnum.IN_PROGRESS.getValue())){
                wrapper.eq(SwoTicketDO :: getIsAcceptance,true).eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.IN_PROGRESS.getStatus());
            }else if(Objects.equals(pageReqVO.getStatus(), SwoServiceStatusEnum.COMPLETED.getValue())){
                wrapper.eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.COMPLETED.getStatus());
            }
        }
        wrapper.eq(SwoTicketDO::getServiceType,SwoServiceTypeEnum.MAINTENANCE.getValue());
        wrapper.ne(SwoTicketDO::getStatus,TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus());
        wrapper.and(ObjectUtil.isNotEmpty(pageReqVO.getSearchBox()), w ->
                w.like(SwoTicketDO::getCustomerName, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getCustomerMobile, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getContactMobile, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getRoomFullName, pageReqVO.getSearchBox())
                        .or().like(SwoTicketDO::getWoNumber,pageReqVO.getSearchBox())
        );

        wrapper.orderByDesc(SwoTicketDO::getCreateTime);
        Page<SwoTicketDO> page = swoTicketMapper.selectPage(new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), wrapper);
        List<SwoTicketRespVO> respVOS = SwoTicketConvert.INSTANCE.convertLists(page.getRecords());
        respVOS.stream().forEach(swoTicketRespVO -> {
            if(ObjectUtil.isNotEmpty(swoTicketRespVO.getServiceType())){
                swoTicketRespVO.setServiceTypeName(SwoServiceTypeEnum.getNameByValue(swoTicketRespVO.getServiceType()));
            }
            if(ObjectUtil.isNotEmpty(swoTicketRespVO.getAssignee())) {
                AdminUserRespDTO checkedData = adminUserApi.getUser(swoTicketRespVO.getAssignee()).getCheckedData();
                swoTicketRespVO.setFollowUpUserName(ObjectUtil.isNotEmpty(checkedData) ? checkedData.getNickname() : null);
            }
            if(!swoTicketRespVO.getIsAcceptance()){
                swoTicketRespVO.setHandleState(SwoServiceStatusEnum.PENDING_ORDERS.getName());
            }
            if(swoTicketRespVO.getStatus()==TicketServiceStatusEnum.IN_PROGRESS.getStatus() && swoTicketRespVO.getIsAcceptance()){
                swoTicketRespVO.setHandleState(SwoServiceStatusEnum.IN_PROGRESS.getName());
            }
            if(swoTicketRespVO.getStatus()==TicketServiceStatusEnum.COMPLETED.getStatus()){
                swoTicketRespVO.setHandleState(SwoServiceStatusEnum.COMPLETED.getName());
            }

        });
        return new PageResult<>(respVOS, page.getTotal());
    }

    /**
     * 小程序服务工单标记
     * @param swoMarkerReqVO
     */
    @Override
    public Boolean setMarker(SwoMarkerReqVO swoMarkerReqVO,Long userId) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(swoMarkerReqVO.getSwoTicketId());
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        AdminUserRespDTO loginUserInfo = adminUserApi.getUser(userId).getCheckedData();

        SwoTicketAssignRecordCreateReqVO createReqVO = new SwoTicketAssignRecordCreateReqVO();
        createReqVO.setSwoTicketId(swoTicketDO.getId());
        createReqVO.setOperatorsUserId(userId);
        createReqVO.setAssignee(swoTicketDO.getAssignee());
        createReqVO.setCustomerName(swoTicketDO.getCustomerName());

        StringBuilder sb = new StringBuilder();
        sb.append(loginUserInfo.getNickname());
        sb.append("进行了标记：");
        sb.append(swoMarkerReqVO.getRemark());
        createReqVO.setRecord(sb.toString());
        swoTicketAssignRecordService.createSwoTicketAssignRecord(createReqVO);
        return true;
    }

    /**
     * 生成账单
     * @param swoBillReqVO
     * @return
     */

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addBill(SwoBillReqVO swoBillReqVO) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(swoBillReqVO.getSwoTicketId());
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        // 更新维修工单为有偿维修， 累计维修金额
        swoTicketDO.setNeedPay(true);
        BigDecimal amountOfRepairs = swoTicketDO.getAmountOfRepairs();
        amountOfRepairs = ObjectUtil.isNotEmpty(amountOfRepairs) ? amountOfRepairs : BigDecimal.ZERO;
        swoTicketDO.setAmountOfRepairs(amountOfRepairs.add(swoBillReqVO.getFee()));
        swoTicketMapper.updateById(swoTicketDO);

        Long roomId = swoTicketDO.getRoomId();
        BigDecimal fee = swoBillReqVO.getFee();
        LocalDate dueDate = swoBillReqVO.getDueDate();
        RentOrderCreateMeterReq rentOrderCreateMeterReq=new RentOrderCreateMeterReq();
        rentOrderCreateMeterReq.setRoomId(roomId);
        rentOrderCreateMeterReq.setStartTime(LocalDate.now());
        rentOrderCreateMeterReq.setEndTime(LocalDate.now());
        rentOrderCreateMeterReq.setOughtPayTime(dueDate);
        rentOrderCreateMeterReq.setOrderType(OrderTypeEnum.CUSTOM.getCode());

        List<OrderFeesMeterVo> voList =new ArrayList<>();
        OrderFeesMeterVo orderFeesMeterVo =new OrderFeesMeterVo();
        orderFeesMeterVo.setFeeSort(OrderFeeSortTypeEnum.ONCE.getCode());
        orderFeesMeterVo.setFeeId(11L);
        orderFeesMeterVo.setFeeName("维修费");
        orderFeesMeterVo.setFeeAmount(fee);
        orderFeesMeterVo.setPaymentCycle(1);
        orderFeesMeterVo.setDiscountSort(OrderFeeSortTypeEnum.ONCE.getCode());
        orderFeesMeterVo.setDiscountOrder(OrderDiscountEnum.FIRST.getCode());
        orderFeesMeterVo.setDiscountType(OrderDiscountEnum.RATE.getCode());
        voList.add(orderFeesMeterVo);
        rentOrderCreateMeterReq.setOrderFeesVo(voList);
        rentOrderCreateMeterReq.setSowTicketId(swoBillReqVO.getSwoTicketId());
        contractRentOrderApi.createOrder(rentOrderCreateMeterReq).getData();
        return true;
    }

    @Override
    public void updateMember(Long userId, Long oldUserId, String oldMobile, String mobile, String userName) {
        swoTicketMapper.updateMember(userId,oldUserId,mobile,userName);
        swoTicketMapper.updateMemberByMobile(oldMobile,mobile,userName);
    }


    /**
     * 关闭服务工单申请
     * @param loginUser
     * @param shutdownSwoTicketReqVO
     * @return
     */
    @Override
    public Boolean shutdown(LoginUser loginUser, ShutdownSwoTicketReqVO shutdownSwoTicketReqVO) {
        SwoTicketDO swoTicketDO = swoTicketMapper.selectById(shutdownSwoTicketReqVO.getSwoTicketId());
        if(ObjectUtil.isEmpty(swoTicketDO)){
            throw exception(SWO_TICKET_NOT_EXISTS);
        }
        // 状态改为已完成
        swoTicketDO.setStatus(TicketServiceStatusEnum.COMPLETED.getStatus());
        swoTicketDO.setCompleteTime(LocalDateTime.now());
        swoTicketDO.setShutdownTime(LocalDateTime.now());
        // 进度改为已处理
        swoTicketDO.setProcessStatus(TicketServiceProcessStatusEnum.PROCESS_STATUS_PROCESSED.getProcessStatus());
        swoTicketMapper.updateById(swoTicketDO);

        SwoTicketAssignRecordCreateReqVO createReqVO = new SwoTicketAssignRecordCreateReqVO();
        createReqVO.setSwoTicketId(swoTicketDO.getId());
        createReqVO.setOperatorsUserId(loginUser.getId());
        createReqVO.setCustomerName(swoTicketDO.getCustomerName());
        StringBuilder sb = new StringBuilder();
        sb.append(loginUser.getNickname());
        sb.append(" 关闭了工单");
        createReqVO.setRecord(sb.toString());
        createReqVO.setRemark(shutdownSwoTicketReqVO.getShutdownRemark());
        swoTicketAssignRecordService.createSwoTicketAssignRecord(createReqVO);
        return Boolean.TRUE;
    }
    /**
     * 工单数据汇总
     * @param startTime
     * @param endTime
     * @param tenantId
     * @return
     */
    @Override
    public PageResult<SwoTicketSummaryCountRespVO> getSummaryList(LocalDate startTime, LocalDate endTime, Long tenantId,Integer pageSize,Integer pageNum) {
        List<SwoTicketSummaryCountRespVO> list= new ArrayList<>();
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SwoTicketDO::getTenantId, tenantId);
        if (ObjectUtil.isNotEmpty(startTime)) {
            wrapper.ge(SwoTicketDO::getCreateTime,startTime);
        }
        if (ObjectUtil.isNotEmpty(endTime)) {
            wrapper.le(SwoTicketDO::getCreateTime,endTime);
        }
        List<SwoTicketDO> swoTicketDOS = swoTicketMapper.selectList(wrapper);
        List<Long> collect = swoTicketDOS.stream().map(SwoTicketDO::getApartmentId).distinct().collect(Collectors.toList());
        CommonResult<List<ApartmentListDto>> listCommonResult = apartmentApi.listByIds(collect);
        List<ApartmentListDto> data = listCommonResult.getData();
        HashMap<Long, String> map = new HashMap<>();
        if (CollectionUtil.isNotEmpty(data)) {
            for (ApartmentListDto datum : data) {
                map.put(datum.getId(),datum.getName());
            }
        }
        if (CollectionUtil.isNotEmpty(collect)) {
            for (Long apartmentId : collect) {
                SwoTicketSummaryCountRespVO swoTicketSummaryCountRespVO = new SwoTicketSummaryCountRespVO();
                swoTicketSummaryCountRespVO.setApartmentName(map.get(apartmentId));
                int cleanCount = 0;
                int maintenanceCount = 0;
                int otherCount = 0;
                int completeCount=0;
                List<SwoTicketDO> swoTicketDOList = swoTicketDOS.stream().filter(swoTicketDO -> apartmentId.equals(swoTicketDO.getApartmentId())).collect(Collectors.toList());
                for (SwoTicketDO swoTicketDO : swoTicketDOList) {
                    int serviceType = swoTicketDO.getServiceType();
                    switch(serviceType){
                        case 1 :
                            maintenanceCount++;
                            break;
                        case 2 :
                            cleanCount++;
                            break;
                        case 3 :
                            otherCount++;
                            break;
                    }
                    if (swoTicketDO.getStatus()==TicketServiceStatusEnum.COMPLETED.getStatus()) {
                        completeCount++;
                    }
                }
                swoTicketSummaryCountRespVO.setCleanCount(cleanCount);
                swoTicketSummaryCountRespVO.setMaintenanceCount(maintenanceCount);
                swoTicketSummaryCountRespVO.setOtherCount(otherCount);
                int totalAmount = cleanCount+maintenanceCount+otherCount;
                swoTicketSummaryCountRespVO.setTotalAmount(totalAmount);
                double result =(double)completeCount / totalAmount;
                result = Math.round(result * 100.0) / 100.0;
                swoTicketSummaryCountRespVO.setCompletionRate(result);
                list.add(swoTicketSummaryCountRespVO);
            }
        }

        int fromIndex = (pageNum-1) * pageSize;
        int toIndex = Math.min(fromIndex + pageSize, list.size());
        List<SwoTicketSummaryCountRespVO> pagedList = list.subList(fromIndex, toIndex);
        return new PageResult<>(pagedList, (long) list.size());
    }

    @Override
    public SwoTickerExportResp getSummaryListExport(LocalDate startTime, LocalDate endTime, Long tenantId, Integer pageSize, Integer pageNum) {
        PageResult<SwoTicketSummaryCountRespVO> summaryList = getSummaryList(startTime, endTime, tenantId, pageSize, pageNum);
        String fileName = "工单报表数据导出.xlsx";
        List<SwoTicketSummaryCountRespVO> list = summaryList.getList();
        List<SwoTickerExportVO> swoTickerExportVOS = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(list)) {
            for (SwoTicketSummaryCountRespVO swoTicketSummaryCountRespVO : list) {
                SwoTickerExportVO swoTickerExportVO = new SwoTickerExportVO();
                swoTickerExportVO.setCleanCount(swoTicketSummaryCountRespVO.getCleanCount());
                swoTickerExportVO.setCompletionRate(swoTicketSummaryCountRespVO.getCompletionRate());
                swoTickerExportVO.setMaintenanceCount(swoTicketSummaryCountRespVO.getMaintenanceCount());
                swoTickerExportVO.setOtherCount(swoTicketSummaryCountRespVO.getOtherCount());
                swoTickerExportVO.setTotalAmount(swoTicketSummaryCountRespVO.getTotalAmount());
                swoTickerExportVO.setApartmentName(swoTicketSummaryCountRespVO.getApartmentName());
                swoTickerExportVOS.add(swoTickerExportVO);
            }
        }
        ExcelWriter excelWriter = EasyExcel.write(fileName, SwoTickerExportVO.class).build();
        WriteSheet sheet = EasyExcel.writerSheet("sheet1").head(SwoTickerExportVO.class).build();
        excelWriter.write(swoTickerExportVOS, sheet);
        excelWriter.finish();
        String url = oosFileService.oosUploadFile(new File(fileName));
        SwoTickerExportResp resp = new SwoTickerExportResp();
        resp.setUrl(url);
        resp.setFileName(fileName);
        return resp;
    }
    private String convertStatus(int status) {
        if(TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus() == status){
            return "未受理";
        } else if (TicketServiceStatusEnum.IN_PROGRESS.getStatus() == status) {
            return "受理中";
        } else if (TicketServiceStatusEnum.COMPLETED.getStatus() == status) {
            return "已结束";
        }
        return "";
    }


    private Long countByProcessStatus(int processStatus, Integer serviceType) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SwoTicketDO::getProcessStatus, processStatus);
        wrapper.eq(ObjectUtil.isNotEmpty(serviceType), SwoTicketDO::getServiceType, serviceType);
        return swoTicketMapper.selectCount(wrapper);
    }

    private Long countByStatus(int status, Integer serviceType) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(SwoTicketDO::getStatus, status);
        wrapper.eq(ObjectUtil.isNotEmpty(serviceType), SwoTicketDO::getServiceType, serviceType);
        return swoTicketMapper.selectCount(wrapper);
    }

    /**
     * 获得维修完成率
     * 已完成/（待处理+处理中+处理完成），关单的和删除的不纳入计算，需要有时间筛选，以创建时间为准，默认本周。
     * @param maintenanceRateReqVO
     * @return
     */
    @Override
    public MaintenanceRateRespVO maintenanceRate(MaintenanceRateReqVO maintenanceRateReqVO) {
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        // 时间段内
        wrapper.between(SwoTicketDO::getCreateTime, maintenanceRateReqVO.getStartTime(), maintenanceRateReqVO.getEndTime());
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 时间段内非关单的所有维修单
        List<SwoTicketDO> swoTicketDOS = swoTicketMapper.selectList(wrapper);
        if(ObjectUtil.isEmpty(swoTicketDOS)){
            return new MaintenanceRateRespVO();
        }
        // 分组后统计数量
        Map<Integer, Long> map = swoTicketDOS.stream().collect(Collectors.groupingBy(SwoTicketDO::getStatus, Collectors.counting()));
        // 已完成的数量
        Long completedNum = map.containsKey(TicketServiceStatusEnum.COMPLETED.getStatus()) ? map.get(TicketServiceStatusEnum.COMPLETED.getStatus()) : 0L;
        // 处理中的数量
        Long inProgressNum = map.containsKey(TicketServiceStatusEnum.IN_PROGRESS.getStatus()) ? map.get(TicketServiceStatusEnum.IN_PROGRESS.getStatus()) : 0L;
        // 待分配的数量
        Long pendingOrdersNum = map.containsKey(TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus()) ? map.get(TicketServiceStatusEnum.TO_BE_ASSIGNED.getStatus()) : 0L;
        // 已完成
        BigDecimal completedRate = new BigDecimal(completedNum).divide(new BigDecimal(swoTicketDOS.size()), 4, RoundingMode.HALF_UP);
        // 处理中
        BigDecimal inProgressRate = new BigDecimal(inProgressNum).divide(new BigDecimal(swoTicketDOS.size()), 4, RoundingMode.HALF_UP);
        // 待分配
        BigDecimal pendingRate = new BigDecimal(pendingOrdersNum).divide(new BigDecimal(swoTicketDOS.size()), 4, RoundingMode.HALF_UP);

        // 累计维修率
        wrapper = Wrappers.lambdaQuery();
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 非关单的所有维修单
        List<SwoTicketDO> swoTickets = swoTicketMapper.selectList(wrapper);
        // 已完成的维修单数量
        long allCompletedNum = swoTickets.stream().filter(p -> TicketServiceStatusEnum.COMPLETED.getStatus().equals(p.getStatus())).count();
        // 总的维修完成率
        BigDecimal allCompletedRate = new BigDecimal(allCompletedNum).divide(new BigDecimal(swoTickets.size()), 4, RoundingMode.HALF_UP);
        return new MaintenanceRateRespVO().setCompletedRate(completedRate).setInProgressRate(inProgressRate).setPendingRate(pendingRate).setAllCompletedRate(allCompletedRate);
    }

    /**
     * 统计是否有偿维修
     * @param maintenanceRateReqVO
     * @return
     */
    @Override
    public NeedPayRateRespVO isNeedPayRate(MaintenanceRateReqVO maintenanceRateReqVO) {
        log.info("[统计是否有偿维修] 请求参数", JsonUtils.toJsonString(maintenanceRateReqVO));
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        // 时间段内
        wrapper.between(SwoTicketDO::getCreateTime, maintenanceRateReqVO.getStartTime(), maintenanceRateReqVO.getEndTime());
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 已完成
        wrapper.eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.COMPLETED.getStatus());
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 时间段内非关单且已完成的所有维修单
        List<SwoTicketDO> swoTicketDOS = swoTicketMapper.selectList(wrapper);
        log.info("[统计是否有偿维修] 时间段内非关单的所有维修单数量:{}", swoTicketDOS.size());
        if(ObjectUtil.isEmpty(swoTicketDOS)){
            return new NeedPayRateRespVO();
        }
        // 有偿维修数量
        long needPayNum = swoTicketDOS.stream().filter(p -> ObjectUtil.isEmpty(p.getNeedPay()) ? false : p.getNeedPay()).collect(Collectors.toList()).size();
        log.info("[统计是否有偿维修] 有偿维修数量:{}", needPayNum);
        // 无偿维修数量
        long notNeedPayNum = swoTicketDOS.size() - needPayNum;
        // 有偿维修率
        BigDecimal needPayRate = new BigDecimal(needPayNum).divide(new BigDecimal(swoTicketDOS.size()), 4, RoundingMode.HALF_UP);
        // 无偿维修率
        BigDecimal notNeedPayRate = new BigDecimal(notNeedPayNum).divide(new BigDecimal(swoTicketDOS.size()), 4, RoundingMode.HALF_UP);

        // 累计有偿维修金额
        wrapper = Wrappers.lambdaQuery();
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 有偿维修
        wrapper.eq(SwoTicketDO::getNeedPay, true);
        // 已完成
        wrapper.eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.COMPLETED.getStatus());
        // 非关单且已完成的所有维修单
        List<SwoTicketDO> swoTickets = swoTicketMapper.selectList(wrapper);
        // 计算总金额
        AtomicReference<BigDecimal> totalAmount = new AtomicReference<>(BigDecimal.ZERO);
        swoTickets.forEach(swoTicket -> {
            totalAmount.set(totalAmount.get().add(swoTicket.getAmountOfRepairs()));
        });
        log.info("[统计是否有偿维修] 累计有偿维修金额:{}", totalAmount.get());
        return new NeedPayRateRespVO().setNotNeedPayRate(notNeedPayRate).setNeedPayRate(needPayRate).setTotalAmount(totalAmount.get());
    }

    /**
     * 统计评价率
     * @param maintenanceRateReqVO
     * @return
     */
    @Override
    public EvaluateRateRespVO evaluateRate(MaintenanceRateReqVO maintenanceRateReqVO) {
        log.info("[统计评价率] 请求参数", JsonUtils.toJsonString(maintenanceRateReqVO));
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        // 时间段内
        wrapper.between(SwoTicketDO::getCreateTime, maintenanceRateReqVO.getStartTime(), maintenanceRateReqVO.getEndTime());
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 时间段内非关单的所有维修单
        List<SwoTicketDO> swoTicketDOS = swoTicketMapper.selectList(wrapper);
        log.info("[统计评价率] 时间段内非关单的所有维修单数量:{}", swoTicketDOS.size());
        if(ObjectUtil.isEmpty(swoTicketDOS)){
            return new EvaluateRateRespVO();
        }
        // 已评价的维修单
        List<SwoTicketDO> evaluatedSwoTicketList = swoTicketDOS.stream().filter(p -> ObjectUtil.isEmpty(p.getEvaluatedOrNot()) ? false : p.getEvaluatedOrNot()).collect(Collectors.toList());
        log.info("[统计评价率] 已评价的维修单数量:{}", evaluatedSwoTicketList.size());
        // 按评分分组
        Map<Integer, Long> evaluatedMap = evaluatedSwoTicketList.stream().collect(Collectors.groupingBy(SwoTicketDO::getRating, Collectors.counting()));
        Map<Integer, QuantityRateDto> map = new HashMap<>();
        // 1分数量
        // long one = evaluatedMap.containsKey(1) ? evaluatedMap.get(1) : 0;
        // map.put(1, new BigDecimal(one).divide(new BigDecimal(evaluatedSwoTicketList.size()), 4, RoundingMode.HALF_UP));
        evaluatedMap.forEach((key,value) ->{
            QuantityRateDto quantityRateDto = new QuantityRateDto();
            quantityRateDto.setQuantity(value);
            quantityRateDto.setRate(new BigDecimal(value).divide(new BigDecimal(evaluatedSwoTicketList.size()), 4, RoundingMode.HALF_UP));
            map.put(key, quantityRateDto);
            log.info("[统计评价率] {}分评价数量:{}, 占比:{}", key, value, quantityRateDto.getRate());
        });

        // 截止今日服务评价占比
        wrapper = Wrappers.lambdaQuery();
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 非关单的所有维修单
        List<SwoTicketDO> swoTickets = swoTicketMapper.selectList(wrapper);
        log.info("[统计评价率] 截止今日非关单的所有维修单数量:{}", swoTickets.size());
        BigDecimal evaluatedRate = BigDecimal.ZERO;
        if(ObjectUtil.isNotEmpty(swoTickets)){
            List<SwoTicketDO> evaluatedSwoTickets = swoTickets.stream().filter(p -> ObjectUtil.isEmpty(p.getEvaluatedOrNot()) ? false : p.getEvaluatedOrNot()).collect(Collectors.toList());
            log.info("[统计评价率] 截止今日非关单的所有维修单已评价的数量:{}", evaluatedSwoTickets.size());
            evaluatedRate = new BigDecimal(evaluatedSwoTickets.size()).divide(new BigDecimal(swoTickets.size()), 4, RoundingMode.HALF_UP);
        }
        log.info("[统计评价率] 截止今日非关单的所有维修单评价率:{}", evaluatedRate);
        return new EvaluateRateRespVO().setEvaluatedRate(evaluatedRate).setMap(map);
    }

    /**
     * 统计维修及时率
     * @param maintenanceRateReqVO
     * @return
     */
    @Override
    public TimelyMaintenanceRateRespVO timelyMaintenanceRate(MaintenanceRateReqVO maintenanceRateReqVO) {
        log.info("[统计维修及时率] 请求参数", JsonUtils.toJsonString(maintenanceRateReqVO));
        LambdaQueryWrapper<SwoTicketDO> wrapper = Wrappers.lambdaQuery();
        // 时间段内
        wrapper.between(SwoTicketDO::getCreateTime, maintenanceRateReqVO.getStartTime(), maintenanceRateReqVO.getEndTime());
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 已完成
        wrapper.eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.COMPLETED.getStatus());
        // 时间段内非关单的已完成维修单
        List<SwoTicketDO> swoTicketDOS = swoTicketMapper.selectList(wrapper);
        log.info("[统计维修及时率] 时间段内非关单的已完成维修单数量:{}", swoTicketDOS.size());
        if(ObjectUtil.isEmpty(swoTicketDOS)){
            return new TimelyMaintenanceRateRespVO();
        }
        Map<String, BigDecimal> map = new TreeMap<>();
        // 按日期分组
        Map<String, List<SwoTicketDO>> swoTicketMap = swoTicketDOS.stream().collect(Collectors.groupingBy(p -> LocalDateTimeUtil.format(p.getCreateTime(), "yyyy-MM-dd")));
        swoTicketMap.forEach((key, value) -> {
            // 某一天的维修单
            List<SwoTicketDO> swoTickets = swoTicketMap.get(key);
            // 当天创建的维修单超时的数量
            AtomicInteger count = new AtomicInteger(0);
            // 统计维修超时数量
            swoTickets.forEach(swoTicketDO -> {
                // 完成时间在要求时间之后, 超时
                if (ObjectUtil.isNotEmpty(swoTicketDO.getCompleteTime())
                        && ObjectUtil.isNotEmpty(swoTicketDO.getRequiredCompletionTime())
                        && swoTicketDO.getCompleteTime().isAfter(swoTicketDO.getRequiredCompletionTime())) {
                    count.incrementAndGet();
                }
            });
            // 当前的超时率, 超时工单/已完成工单
            BigDecimal timelyMaintenanceRate = new BigDecimal(count.get()).divide(new BigDecimal(value.size()), 4, RoundingMode.HALF_UP);
            map.put(key, timelyMaintenanceRate);
            log.info("[统计维修及时率] 日期:{},超时工单数量:{}, 比例:{}", key, count, timelyMaintenanceRate);
        });

        // 截止今日超时工单占比
        // 所有的已完成工单
        wrapper = Wrappers.lambdaQuery();
        // 非关单
        wrapper.isNull(SwoTicketDO::getShutdownTime);
        // 维修单
        wrapper.eq(SwoTicketDO::getServiceType, SwoServiceTypeEnum.MAINTENANCE.getValue());
        // 已完成
        wrapper.eq(SwoTicketDO::getStatus, TicketServiceStatusEnum.COMPLETED.getStatus());
        // 所有非关单的已完成维修单
        List<SwoTicketDO> swoTickets = swoTicketMapper.selectList(wrapper);
        log.info("[统计维修及时率] 截止今日所有非关单的已完成维修单数量:{}", swoTickets.size());
        // 所有的超时工单
        AtomicInteger count = new AtomicInteger(0);
        swoTickets.forEach(swoTicketDO -> {
            // 完成时间在要求时间之后, 超时
            if (ObjectUtil.isNotEmpty(swoTicketDO.getCompleteTime())
                    && ObjectUtil.isNotEmpty(swoTicketDO.getRequiredCompletionTime())
                    && swoTicketDO.getCompleteTime().isAfter(swoTicketDO.getRequiredCompletionTime())) {
                count.incrementAndGet();
            }
        });
        BigDecimal timelyMaintenanceRate = new BigDecimal(count.get()).divide(new BigDecimal(swoTickets.size()), 4, RoundingMode.HALF_UP);
        log.info("[统计维修及时率] 截止今日所有非关单的已完成维修单超时数量:{}, 超时率:{}", count, timelyMaintenanceRate);
        return new TimelyMaintenanceRateRespVO().setAllTimelyMaintenanceRate(timelyMaintenanceRate).setMap(map);
    }

}

